package unwrap;

import java.util.ArrayList;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import processing.core.PGraphics;
import toxi.geom.Vec2D;
import toxi.geom.Vec3D;
import toxi.math.MathUtils;

public class UTriangle {

	/**
	 * unique triangle id
	 */
	public final int id;
	
	/**
	 * point a
	 */
	public UPoint a;
	/**
	 * point b
	 */
	public UPoint b;
	/**
	 * point c
	 */
	public UPoint c;
	
	
	private USide bc, ca, ab;
	private float alpha, beta, gamma;
	private Vec3D center3d;
	private Vec3D bcBisector3d, caBisector3d, abBisector3d;

	// 2D
	private UPoint2D a2d, b2d, c2d;
	private Vec2D center2d;
	private Vec2D bcBisector2d, caBisector2d, abBisector2d;
	private Vec2D bcInfoText, caInfoText, abInfoText;

	private ArrayList<UPoint> points;

	/**
	 * Constructs a new triangle and give the referred points and an unique
	 * triangle id.
	 */
	public UTriangle(UPoint _a, UPoint _b, UPoint _c, int _id) {
		a = _a;
		b = _b;
		c = _c;

		id = _id;

		bc = new USide(b, c, this);
		ca = new USide(c, a, this);
		ab = new USide(a, b, this);

		update();
	}

	/**
	 * update the triangle.
	 */
	public void update() {
		if (points != null) {
			a = points.get(a.id);
			b = points.get(b.id);
			c = points.get(c.id);

			bc.setPoints(b, c);
			ca.setPoints(c, a);
			ab.setPoints(a, b);
		}

		float lenAB = ab.length;
		float lenBC = bc.length;
		float lenCA = ca.length;
		alpha = (float) Math.acos((lenCA * lenCA + lenAB * lenAB - lenBC
				* lenBC)
				/ (2 * lenCA * lenAB));
		beta = (float) Math
				.acos((lenBC * lenBC + lenAB * lenAB - lenCA * lenCA)
						/ (2 * lenBC * lenAB));
		gamma = (float) Math.acos((lenBC * lenBC + lenCA * lenCA - lenAB
				* lenAB)
				/ (2 * lenBC * lenCA));

		center3d = a.add(b).addSelf(c).scaleSelf(1 / 3f);

		bcBisector3d = b.interpolateTo(c, 0.5f);
		caBisector3d = c.interpolateTo(a, 0.5f);
		abBisector3d = a.interpolateTo(b, 0.5f);
	}

	/**
	 * get side bc
	 */
	USide getBC() {
		return bc;
	}

	/**
	 * get side ca
	 */
	USide getCA() {
		return ca;
	}

	/**
	 * get side ab
	 */
	USide getAB() {
		return ab;
	}

	/**
	 * get the triangle neighbor on side bc
	 */
	public UTriangle getNeighborBC() {
		return bc.neighbor;
	}
	
	/**
	 * get the triangle neighbor on side ca
	 */
	public UTriangle getNeighborCA() {
		return ca.neighbor;
	}
	
	/**
	 * get the triangle neighbor on side ab
	 */
	public UTriangle getNeighborAB() {
		return ab.neighbor;
	}

	/**
	 * get the Angle between the two triangle on side bc
	 */
	public float getAngleBC() {
		if (bc.neighbor == null)
			return 9999999;
		return bc.getAngle();
	}

	/**
	 * get the Angle between the two triangle on side ca
	 */
	public float getAngleCA() {
		if (ca.neighbor == null)
			return 9999999;
		return ca.getAngle();
	}

	/**
	 * get the Angle between the two triangle on side ab
	 */
	public float getAngleAB() {
		if (ab.neighbor == null)
			return 9999999;
		return ab.getAngle();
	}

	/**
	 * get the triangle angle alpha
	 */
	public float getAlpha() {
		return alpha;
	}

	/**
	 * get the triangle angle beta
	 */
	public float getBeta() {
		return beta;
	}

	/**
	 * get the triangle angle gamma
	 */
	public float getGamma() {
		return gamma;
	}

	/**
	 * get the triangle center in 3d
	 */
	public Vec3D getCenter3d() {
		return center3d;
	}

	/**
	 * get the triangle bisector on side bc
	 */
	public Vec3D getBCBisector3d() {
		return bcBisector3d;
	}

	/**
	 * get the triangle bisector on side ca
	 */
	public Vec3D getCABisector3d() {
		return caBisector3d;
	}

	/**
	 * get the triangle bisector on side ab
	 */
	public Vec3D getABBisector3d() {
		return abBisector3d;
	}

	/**
	 * get the triangle Point A in 2d
	 */
	UPoint2D getA2d() {
		return a2d;
	}

	/**
	 * get the triangle Point B in 2d
	 */
	UPoint2D getB2d() {
		return b2d;
	}

	/**
	 * get the triangle Point C in 2d
	 */
	UPoint2D getC2d() {
		return c2d;
	}

	/**
	 * get the triangle center in 2d
	 */
	public Vec2D getCenter2d() {
		return center2d;
	}

	/**
	 * get the triangle bisector on side bc in 2d
	 */
	public Vec2D getBCBisector2d() {
		return bcBisector2d;
	}

	/**
	 * get the triangle bisector on side ca in 2d
	 */
	public Vec2D getCABisector2d() {
		return caBisector2d;
	}

	/**
	 * get the triangle bisector on side ab in 2d
	 */
	public Vec2D getABBisector2d() {
		return abBisector2d;
	}

	/**
	 * update the triangle points
	 */
	void updatePoints(ArrayList<UPoint> _points) {
		points = _points;
	}

	/**
	 * draw the triangle in 3d.
	 */
	public void draw3d() {
		draw3d(Unwrap.g);
	}

	/**
	 * draw the triangle in 3d.
	 */
	public void draw3d(PGraphics pg) {
		pg.beginShape();
		pg.vertex(a.x, a.y, a.z);
		pg.vertex(b.x, b.y, b.z);
		pg.vertex(c.x, c.y, c.z);
		pg.endShape(PConstants.CLOSE);
	}

	/**
	 * draw the triangle normals with default length.
	 */
	public void drawNormal() {
		drawNormal(Unwrap.g, 10);
	}

	/**
	 * draw the triangle normals with given length.
	 */
	public void drawNormal(float scale) {
		drawNormal(Unwrap.g, scale);
	}

	/**
	 * draw the triangle normals with given length.
	 */
	public void drawNormal(PGraphics pg, float scale) {
		Vec3D pos = getNormal(a, b, c, scale).addSelf(center3d);
		pg.line(pos.x, pos.y, pos.z, center3d.x, center3d.y, center3d.z);
	}

	/**
	 * get the Triangle normal.
	 */
	public Vec3D getNormal() {
		return getNormal(a, b, c, 1).addSelf(center3d);
	}

	/**
	 * get the Normal from given Points.
	 */
	public static Vec3D getNormal(Vec3D p1, Vec3D p2, Vec3D p3, float scale) {
		Vec3D normal = p1.sub(p2).cross(p1.sub(p3)).normalize();
		return normal.scaleSelf(scale);
	}

	/**
	 * draw the flatten triangle in 2d on the given position.
	 */
	public void draw2d(Vec2D pos) {
		draw2d(pos, new Vec2D(pos.x + 100, pos.y), ab, Unwrap.g);
	}

	/**
	 * draw the flatten triangle in 2d on the given position.
	 */
	public void draw2d(Vec2D pos, PGraphics pg) {
		draw2d(pos, new Vec2D(pos.x + 100, pos.y), ab, pg);
	}

	/**
	 * draw the flatten triangle in 2d on the given position and pass a Vector
	 * for its rotation, direction.
	 */
	public void draw2d(Vec2D pos, Vec2D dir) {
		draw2d(pos, dir, ab, Unwrap.g);
	}

	/**
	 * draw the flatten triangle in 2d on the given position and pass a Vector
	 * for its rotation, direction.
	 */
	public void draw2d(Vec2D pos, Vec2D dir, PGraphics pg) {
		draw2d(pos, dir, ab, pg);
	}

	/**
	 * draw the flatten triangle in 2d on the given position and pass a Vector
	 * for its rotation, direction. the own, given Side a,b or c define the
	 * bottom of the triangle.
	 */
	public void draw2d(Vec2D pos, Vec2D dir, USide n) {
		draw2d(pos, dir, n, Unwrap.g);
	}

	/**
	 * draw the flatten triangle in 2d on the given position and pass a Vector
	 * for its rotation, direction. the own, given Side a,b or c define the
	 * bottom of the triangle.
	 */
	public void draw2d(Vec2D pos, Vec2D dir, USide n, PGraphics pg) {
		get2d(pos, dir, n);
		pg.beginShape();
		pg.vertex(a2d.x, a2d.y);
		pg.vertex(b2d.x, b2d.y);
		pg.vertex(c2d.x, c2d.y);
		pg.endShape(PConstants.CLOSE);
	}

	/**
	 * set the flatten trianglepoints from to the given position.
	 */
	public void set2d(Vec2D pos) {
		set2d(pos, new Vec2D(pos.x + 100, pos.y), ab);
	}

	/**
	 * set the flatten trianglepoints from to the given position and its
	 * rotation, direction.
	 */
	public void set2d(Vec2D pos, Vec2D dir) {
		set2d(pos, dir, ab);
	}

	/**
	 * set the flatten trianglepoints from to the given position and its
	 * rotation, direction. the own, given Side a,b or c define the bottom of
	 * the triangle.
	 */
	public void set2d(Vec2D pos, Vec2D dir, USide n) {
		get2d(pos, dir, n);
	}

	/**
	 * get the flatten trianglepoints from to the given position.
	 */
	public Vec2D[] get2d(Vec2D pos) {
		return get2d(pos, new Vec2D(pos.x + 100, pos.y), ab);
	}

	/**
	 * get the flatten trianglepoints from to the given position and its
	 * rotation, direction.
	 */
	public Vec2D[] get2d(Vec2D pos, Vec2D dir) {
		return get2d(pos, dir, ab);
	}

	/**
	 * get the flatten trianglepoints from to the given position and its
	 * rotation, direction. the own, given Side a,b or c define the bottom of
	 * the triangle.
	 */
	public Vec2D[] get2d(Vec2D pos, Vec2D dir, USide n) {

		if (n == ca) {
			c2d = new UPoint2D(pos, c.id);
			a2d = new UPoint2D(getPoint(c2d, dir, 0, ca.length), a.id);
			b2d = new UPoint2D(getPoint(c2d, a2d, gamma, bc.length), b.id);
		}

		if (n == bc) {
			b2d = new UPoint2D(pos, b.id);
			c2d = new UPoint2D(getPoint(b2d, dir, 0, bc.length), c.id);
			a2d = new UPoint2D(getPoint(b2d, c2d, beta, ab.length), a.id);
		}

		if (n == ab) {
			a2d = new UPoint2D(pos, a.id);
			b2d = new UPoint2D(getPoint(a2d, dir, 0, ab.length), b.id);
			c2d = new UPoint2D(getPoint(a2d, b2d, alpha, ca.length), c.id);
		}

		center2d = a2d.add(b2d).addSelf(c2d).scaleSelf(1 / 3f);

		bcBisector2d = b2d.interpolateTo(c2d, 0.5f);
		caBisector2d = c2d.interpolateTo(a2d, 0.5f);
		abBisector2d = a2d.interpolateTo(b2d, 0.5f);

		return new Vec2D[] { a2d, b2d, c2d };
	}

	/**
	 * calculate new triangle point.
	 */
	private Vec2D getPoint(Vec2D p0, Vec2D p1, float angle, float len) {
		Vec2D pp1 = p0.sub(p1);
		Vec2D p2 = new Vec2D(len, 0).getRotated((float) Math
				.atan2(pp1.y, pp1.x)
				+ MathUtils.PI - angle);
		p2.addSelf(p0);
		return p2;
	}

	/**
	 * calculate points for info text.
	 */
	private void setInfoPoints() {
		bcInfoText = bcBisector2d.interpolateTo(getIncenter2d(), 0.7f);
		caInfoText = caBisector2d.interpolateTo(getIncenter2d(), 0.7f);
		abInfoText = abBisector2d.interpolateTo(getIncenter2d(), 0.7f);
	}

	/**
	 * draw assembling info text.
	 */
	public void drawNeighborInfoText(float size) {
		drawNeighborInfoText(Unwrap.g, Unwrap.font, size);
	}

	/**
	 * draw assembling info text.
	 */
	public void drawNeighborInfoText(float size, PGraphics pg) {
		drawNeighborInfoText(pg, Unwrap.font, size);
	}

	/**
	 * draw assembling info text.
	 */
	public void drawNeighborInfoText(PGraphics pg, PFont font, float size) {
		if (font != null) {
			setInfoPoints();
			pg.textFont(font, size + 10);
			pg.text("" + id, getIncenter2d().x, getIncenter2d().y);
			pg.textFont(font, size);

			if (bc.neighbor != null)
				pg.text(bc.neighbor.id, bcInfoText.x, bcInfoText.y);

			if (ca.neighbor != null)
				pg.text(ca.neighbor.id, caInfoText.x, caInfoText.y);

			if (ab.neighbor != null)
				pg.text(ab.neighbor.id, abInfoText.x, abInfoText.y);
		}
	}

	/**
	 * get 2d triangle incentre
	 */
	public Vec2D getIncenter2d() {
		Vec2D incentre = new Vec2D();

		float abc = bc.length + ca.length + ab.length;
		incentre.x = (bc.length * a2d.x + ca.length * b2d.x + ab.length * c2d.x)
				/ abc;
		incentre.y = (bc.length * a2d.y + ca.length * b2d.y + ab.length * c2d.y)
				/ abc;

		return incentre;
	}

	/**
	 * get 3d triangle incentre
	 */
	public Vec3D getIncenter3d() {
		Vec3D incentre = new Vec3D();

		float abc = bc.length + ca.length + ab.length;
		incentre.x = (bc.length * a.x + ca.length * b.x + ab.length * c.x)
				/ abc;
		incentre.y = (bc.length * a.y + ca.length * b.y + ab.length * c.y)
				/ abc;
		incentre.z = (bc.length * a.z + ca.length * b.z + ab.length * c.z)
				/ abc;

		return incentre;
	}

	// ///INTERSECTION

	UIntersection intS = new UIntersection();

	/**
	 * find triangle triangle intersection
	 */
	public boolean findIntersection(UTriangle t) {
		if (intS.intersection(this, t) == true)
			return true;

		return false;
	}

	/**
	 * find 2d triangle triangle intersection
	 */
	public boolean find2dIntersection(UTriangle t) {
		if (a2d == null || b2d == null || c2d == null || t.getA2d() == null
				|| t.getB2d() == null || t.getC2d() == null)
			return false;

		if (intS.intersection2d(this, t) == true)
			return true;

		return false;
	}

	// ///CLIP
	
	UClip abClip = null, bcClip = null, caClip = null;

	/**
	 * draw assembling clips
	 */
	public void drawClips() {
		drawClips(Unwrap.g);
	}

	/**
	 * draw assembling clips
	 */
	public void drawClips(PGraphics pg) {
		if (a2d == null || b2d == null || c2d == null)
			return;

		Unwrap.clip.drawClip(ab, a2d, b2d, pg);
		Unwrap.clip.drawClip(bc, b2d, c2d, pg);
		Unwrap.clip.drawClip(ca, c2d, a2d, pg);
	}

}
